﻿using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Globalization;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace CRM_Generic_Rollup_Solution.Plugins
{
    public class RollupPlugin : IPlugin
    {
        /*
         * Rollup will be always on Child entity.
         * Needed info
         * Type of Rollup - Counter or Sum
         * Child entity schema name
         * Child entity attribute schema name in case of Sum 
         * Parent Entity Entity Reference schema name on Child Entity
         * Parent Entity Schema Name
         * Parent Entity Aggregate Attribute Schema Name
         * */

        protected Entity preImageEntity = null;
        protected Entity postImageEntity = null;
        protected Entity targetEntity = null;

        /// <summary>
        /// Alias of the image registered for the snapshot of the 
        /// primary entity's attributes before the core platform operation executes.
        /// The image contains the following attributes:
        /// All Attributes
        /// </summary>
        private readonly string preImageAlias = "PreImage";

        /// <summary>
        /// Alias of the image registered for the snapshot of the 
        /// primary entity's attributes after the core platform operation executes.
        /// The image contains the following attributes:
        /// All Attributes
        /// 
        /// Note: Only synchronous post-event and asynchronous registered plug-ins 
        /// have PostEntityImages populated.
        /// </summary>
        private readonly string postImageAlias = "PostImage";


        protected class LocalPluginContext
        {
            internal IServiceProvider ServiceProvider
            {
                get;

                private set;
            }

            internal IOrganizationService OrganizationService
            {
                get;

                private set;
            }

            internal IPluginExecutionContext PluginExecutionContext
            {
                get;

                private set;
            }

            internal ITracingService TracingService
            {
                get;

                private set;
            }

            private LocalPluginContext()
            {
            }

            internal LocalPluginContext(IServiceProvider serviceProvider)
            {
                if (serviceProvider == null)
                {
                    throw new ArgumentNullException("serviceProvider");
                }

                // Obtain the execution context service from the service provider.
                this.PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

                // Obtain the tracing service from the service provider.
                this.TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

                // Obtain the Organization Service factory service from the service provider
                IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

                // Use the factory to generate the Organization Service.
                this.OrganizationService = factory.CreateOrganizationService(this.PluginExecutionContext.UserId);
            }

            internal void Trace(string message)
            {
                if (string.IsNullOrWhiteSpace(message) || this.TracingService == null)
                {
                    return;
                }

                if (this.PluginExecutionContext == null)
                {
                    this.TracingService.Trace(message);
                }
                else
                {
                    this.TracingService.Trace(
                        "{0}, Correlation Id: {1}, Initiating User: {2}",
                        message,
                        this.PluginExecutionContext.CorrelationId,
                        this.PluginExecutionContext.InitiatingUserId);
                }
            }
        }

        protected enum RollupTypeEnum
        {
            /*
             * sum  | avg  | min | max | count(*) | count( attribute name )
             */
            count = 500000000,
            sum = 500000001,
            avg = 500000002
        }

        protected enum DataTypeEnum
        {
            INT = 500000000,
            FLOAT = 500000001,
            DECIMAL = 500000002,
            CURRENCY = 500000003
        }

        protected class RollupSettings
        {
            public string ChildEntity { get; set; }
            public string ChildEntityIdColumn { get; set; }
            public EntityReference ParentEntity { get; set; }
            public string ParentEntityIdColumn { get; set; }
            public string ChildEntityAttribute { get; set; }
            public string ParentEntityAttribute { get; set; }
            public string LookupAttribute { get; set; }
            public RollupTypeEnum RollupType { get; set; }
            //public DataTypeEnum ParentEntityAttributeType { get; set; }
        }
        
        public void Execute(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
            {
                throw new ArgumentNullException("serviceProvider");
            }

            // Construct the Local plug-in context.
            LocalPluginContext localContext = new LocalPluginContext(serviceProvider);

            #region Test Code 
            //Entity tempEntity = new Entity("mkisv_rolluptest");
            //tempEntity["mkisv_name"] = localContext.PluginExecutionContext.PrimaryEntityName;
            ////Target
            //if (localContext.PluginExecutionContext.InputParameters != null && localContext.PluginExecutionContext.InputParameters.Contains("Target"))
            //{
            //    string target = string.Empty;
            //    foreach (KeyValuePair<string, object> KVP in ((Entity)localContext.PluginExecutionContext.InputParameters["Target"]).Attributes)
            //    {
            //        target += KVP.Key;
            //        target += Environment.NewLine;
            //    }
            //    tempEntity["mkisv_target"] = target;
            //}

            //if(localContext.PluginExecutionContext.PreEntityImages != null && localContext.PluginExecutionContext.PreEntityImages.Contains(this.preImageAlias))
            //{
            //    string preEntityImg = string.Empty;
            //    foreach (KeyValuePair<string, object> KVP in localContext.PluginExecutionContext.PreEntityImages[this.preImageAlias].Attributes)
            //    {
            //        preEntityImg += KVP.Key;
            //        preEntityImg += Environment.NewLine;
            //    }
            //    tempEntity["mkisv_preentityimages"] = preEntityImg;
            //}

            //if (localContext.PluginExecutionContext.PostEntityImages != null && localContext.PluginExecutionContext.PostEntityImages.Contains(this.postImageAlias))
            //{
            //    string postEntityImg = string.Empty;
            //    foreach (KeyValuePair<string, object> KVP in localContext.PluginExecutionContext.PostEntityImages[this.postImageAlias].Attributes)
            //    {
            //        postEntityImg += KVP.Key;
            //        postEntityImg += Environment.NewLine;
            //    }
            //    tempEntity["mkisv_postentityimages"] = postEntityImg;
            //}

            //localContext.OrganizationService.Create(tempEntity);
            //return;
            #endregion

            List<RollupSettings> rollupSettings = GetRollupSettings(localContext);

            if (rollupSettings == null || rollupSettings.Count == 0)
            {
                return;
            }

            IPluginExecutionContext context = localContext.PluginExecutionContext;
            preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(this.preImageAlias)) ? context.PreEntityImages[this.preImageAlias] : null;
            postImageEntity = (context.PostEntityImages != null && context.PostEntityImages.Contains(this.postImageAlias)) ? context.PostEntityImages[this.postImageAlias] : null;
            targetEntity = (context.InputParameters != null && context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity) ? (Entity)context.InputParameters["Target"] : null;

            foreach (RollupSettings RS in rollupSettings)
            {
                CallAggregate(localContext, RS);
            }
        }

        private List<RollupSettings> GetRollupSettings(LocalPluginContext context)
        {
            QueryByAttribute query = new QueryByAttribute("mkisv_rollupconfiguration");

            query.AddAttributeValue("mkisv_name", context.PluginExecutionContext.PrimaryEntityName);
            query.AddAttributeValue("statecode", 0);

            query.ColumnSet = new ColumnSet(true);

            EntityCollection ec = context.OrganizationService.RetrieveMultiple(query);

            List<RollupSettings> rollupSettings = new List<RollupSettings>();

            foreach (Entity e in ec.Entities)
            {
                //mkisv_datatype
                RollupSettings rs = new RollupSettings()
                {
                    ChildEntity = e.GetAttributeValue<string>("mkisv_name"),
                    ChildEntityIdColumn = e.GetAttributeValue<string>("mkisv_childentityidattribute"),
                    ChildEntityAttribute = e.GetAttributeValue<string>("mkisv_childentityattributename"),
                    LookupAttribute = e.GetAttributeValue<string>("mkisv_lookupattributename"),
                    ParentEntity = new EntityReference(e.GetAttributeValue<string>("mkisv_parententityname"), Guid.Empty),
                    ParentEntityIdColumn = e.GetAttributeValue<string>("mkisv_parententityidattribute"),
                    ParentEntityAttribute = e.GetAttributeValue<string>("mkisv_parententityattributename"),
                    RollupType = (RollupTypeEnum)e.GetAttributeValue<OptionSetValue>("mkisv_aggregationtype").Value,
                    //ParentEntityAttributeType = (DataTypeEnum)e.GetAttributeValue<OptionSetValue>("mkisv_datatype").Value,
                };

                rollupSettings.Add(rs);
            }
            return rollupSettings;
        }

        private void CallAggregate(LocalPluginContext localContext, RollupSettings rollupSetting)
        {
            if (string.Compare(localContext.PluginExecutionContext.MessageName, "Create", true) == 0)
            {
                if (postImageEntity != null && postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute) != null)
                {
                    //rollupSetting.ChildEntityIdColumn = postImageEntity.Attributes.Where(a => a.Value.GetType() == typeof(System.Guid)).First().Key;
                    rollupSetting.ParentEntity.Id = postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                    CalculateAggregate(localContext, rollupSetting);
                }
            }
            else if (string.Compare(localContext.PluginExecutionContext.MessageName, "Update", true) == 0)
            {
                if (targetEntity != null && targetEntity.Attributes.Contains(rollupSetting.LookupAttribute))
                {
                    //Checking scenario of RegardingObjectid attribute on Task entity where Logical name will be different
                    if (preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute) != null 
                        && string.Compare(preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).LogicalName, rollupSetting.ParentEntity.LogicalName, true) == 0)
                    {
                        //rollupSetting.ChildEntityIdColumn = preImageEntity.Attributes.Where(a => a.Value.GetType() == typeof(System.Guid)).First().Key;
                        rollupSetting.ParentEntity.Id = preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                        CalculateAggregate(localContext, rollupSetting);
                    }
                    //Checking scenario of RegardingObjectid attribute on Task entity where Logical name will be different
                    if (postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute) != null 
                        && string.Compare(postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).LogicalName, rollupSetting.ParentEntity.LogicalName, true) == 0)
                    {
                        //, postImageEntity.GetAttributeValue<EntityReference>("accountid").Id
                        rollupSetting.ParentEntity.Id = postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                        CalculateAggregate(localContext, rollupSetting);
                    }
                }
                else if (targetEntity != null && targetEntity.Attributes.Contains(rollupSetting.ChildEntityAttribute) && postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute) != null)
                {
                    rollupSetting.ParentEntity.Id = postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                    CalculateAggregate(localContext, rollupSetting);
                }

                #region Old Code 
                //if (preImageEntity != null && postImageEntity != null &&
                //    preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute) != null &&
                //    postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute) != null &&
                //    preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id != postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id)
                //{
                //    //preImageEntity.GetAttributeValue<EntityReference>("accountid").Id
                //    //Checking scenario of RegardingObjectid attribute on Task entity where Logical name will be different
                //    if (string.Compare(preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).LogicalName, rollupSetting.ParentEntity.LogicalName, true) == 0)
                //    {
                //        //rollupSetting.ChildEntityIdColumn = preImageEntity.Attributes.Where(a => a.Value.GetType() == typeof(System.Guid)).First().Key;
                //        rollupSetting.ParentEntity.Id = preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                //        CalculateAggregate(localContext, rollupSetting);
                //    }

                //    //, postImageEntity.GetAttributeValue<EntityReference>("accountid").Id
                //    rollupSetting.ParentEntity.Id = postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                //    CalculateAggregate(localContext, rollupSetting);
                //}
                //else if (preImageEntity == null && postImageEntity != null &&
                //    postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute) != null)
                //{
                //    rollupSetting.ParentEntity.Id = postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                //    CalculateAggregate(localContext, rollupSetting);
                //}
                //else if (preImageEntity != null && postImageEntity == null &&
                //    preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute) != null)
                //{
                //    rollupSetting.ParentEntity.Id = preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                //    CalculateAggregate(localContext, rollupSetting);
                //}
                //    //This will take care of scenario other than Count of records.
                //else if (preImageEntity != null && preImageEntity.Contains(rollupSetting.ChildEntityAttribute))
                //{
                //    rollupSetting.ParentEntity.Id = postImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                //    CalculateAggregate(localContext, rollupSetting);
                //}
                #endregion
            }
            else if (string.Compare(localContext.PluginExecutionContext.MessageName, "Delete", true) == 0)
            {
                if (preImageEntity != null && preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute) != null
                     && string.Compare(preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).LogicalName, rollupSetting.ParentEntity.LogicalName, true) == 0)
                {
                    rollupSetting.ParentEntity.Id = preImageEntity.GetAttributeValue<EntityReference>(rollupSetting.LookupAttribute).Id;
                    CalculateAggregate(localContext, rollupSetting, true);
                }
            }
        }

        protected void CalculateAggregate(LocalPluginContext localContext, RollupSettings rollupSetting, bool ignoreThisRecord = false)
        {
            /*
               string fetchXml = string.Format(@"<fetch distinct='false' mapping='logical' aggregate='true'> 
                                                    <entity name='contact'> 
                                                    <attribute name='contactid' alias='contact_count' aggregate='count'/> 
                                                    <filter>
			                                            <condition attribute='accountid' operator='eq' value='{0}' />
		                                            </filter>
                                                    </entity> 
                                              </fetch> ", accountId.ToString());
             */
            string fetchXml = string.Empty;

            if (ignoreThisRecord)
            {
                fetchXml = string.Format(@"<fetch distinct='false' mapping='logical' aggregate='true'> 
                                                    <entity name='{0}'> 
                                                    <attribute name='{1}' alias='A' aggregate='{2}'/> 
                                                    <filter>
			                                            <condition attribute='{3}' operator='eq' value='{4}' />
			                                            <condition attribute='{5}' operator='neq' value='{6}' />
		                                            </filter>
                                                    </entity> 
                                              </fetch> ", rollupSetting.ChildEntity, rollupSetting.ChildEntityAttribute, rollupSetting.RollupType.ToString(), rollupSetting.LookupAttribute, rollupSetting.ParentEntity.Id.ToString(), rollupSetting.ChildEntityIdColumn, localContext.PluginExecutionContext.PrimaryEntityId.ToString());
            }
            else
            {
                fetchXml = string.Format(@"<fetch distinct='false' mapping='logical' aggregate='true'> 
                                                    <entity name='{0}'> 
                                                    <attribute name='{1}' alias='A' aggregate='{2}'/> 
                                                    <filter>
			                                            <condition attribute='{3}' operator='eq' value='{4}' />
		                                            </filter>
                                                    </entity> 
                                              </fetch> ", rollupSetting.ChildEntity, rollupSetting.ChildEntityAttribute, rollupSetting.RollupType.ToString(), rollupSetting.LookupAttribute, rollupSetting.ParentEntity.Id.ToString());
            }


            EntityCollection contact_count_result = localContext.OrganizationService.RetrieveMultiple(new FetchExpression(fetchXml));

            Entity accountObj = new Entity(rollupSetting.ParentEntity.LogicalName);

            accountObj[rollupSetting.ParentEntityIdColumn] = rollupSetting.ParentEntity.Id;
            foreach (var c in contact_count_result.Entities)
            {
                object aggregate = null;

                if (c.Contains("A") && c["A"] != null && ((AliasedValue)c["A"]).Value != null)
                {
                    aggregate = ((AliasedValue)c["A"]).Value;
                }
                accountObj[rollupSetting.ParentEntityAttribute] = aggregate;

                //if (ignoreThisRecord && aggregate2 > 0)
                //{
                //    accountObj[rollupSetting.ParentEntityAttribute] = aggregate2 - 1;
                //}
                //else
                //{
                //    accountObj[rollupSetting.ParentEntityAttribute] = aggregate2;
                //}
            }
            localContext.OrganizationService.Update(accountObj);
        }
    }
}
